package com.sjp.blog.repository;

import org.apache.commons.beanutils.BeanUtils;
import org.hibernate.SQLQuery;
import org.hibernate.transform.Transformers;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BaseRepositoryImpl<M> {
    @PersistenceContext
    private EntityManager em;

    public Class<M> getTClass() {
        try {
            Class<M> tClass = (Class<M>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
            return tClass;
        } catch (Exception e) {
            return null;
        }

    }

    protected EntityManager getEntityManager() {
        return em;
    }

    protected int executeUpdate(String sql, Object... paras){
        Query query = em.createNativeQuery(sql);
        if (paras != null) {
            int i = 1;
            for (Object para : paras) {
                query.setParameter(i++, para);
            }
        }
        return query.executeUpdate();
    }

    protected double findToDouble(String alias, String sql, Object... paras) {
        Map<String, Object> result = findFirstToMap(sql, paras);
        Object o = result.get(alias);
        if (o == null) {
            return 0d;
        }
        return new BigDecimal(o.toString()).doubleValue();
    }

    protected Map<String, Object> findFirstToMap(String sql, Object... paras) {
        List<Map<String, Object>> toMap = findToMap(sql, paras);
        if(toMap.isEmpty()){
            return new HashMap<String, Object>();
        }
        return toMap.get(0);
    }

    protected List<Map<String, Object>> findToMap(String sql, Object... paras) {
        Query query = em.createNativeQuery(sql);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        if (paras != null) {
            int i = 1;
            for (Object para : paras) {
                query.setParameter(i++, para);
            }
        }
        List<Map<String, Object>> resultList = query.getResultList();
        return resultList;
    }

    protected M findFirst(String sql, Object... paras) {
        List<M> list = find(getTClass(), sql, paras);
        return list.isEmpty() ? null : list.get(0);
    }

    protected <T> T findFirst(Class<T> cls, String sql, Object... paras) {
        List<T> list = find(cls, sql, paras);
        return list.isEmpty() ? null : list.get(0);
    }

    protected List<M> find(String sql, Object... paras) {
        return find(getTClass(), sql, paras);
    }

    protected <T> List<T> find(Class<T> cls, String sql, Object... paras) {
        Query query = em.createNativeQuery(sql);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        if (paras != null) {
            int i = 1;
            for (Object para : paras) {
                query.setParameter(i++, para);
            }
        }
        List<Map<String, Object>> resultList = query.getResultList();
        return mapToObjectList(resultList, cls);
    }

    protected Page<M> paginate(int page, int size, String sql, Object... paras) {
        return paginate(getTClass(), page, size, sql, paras);
    }

    protected <T> Page<T> paginate(Class<T> cls, int page, int size, String sql, Object... paras) {

        Query query = em.createNativeQuery(sql);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        if (paras != null) {
            int i = 1;
            for (Object para : paras) {
                query.setParameter(i++, para);
            }
        }
        query.setFirstResult((page - 1)*size);
        query.setMaxResults(size);
        List<Map<String, Object>> resultList = query.getResultList();

        //获取总条数
        String countSql = "select count(*) from ( " + sql + " ) t12345";
        query = em.createNativeQuery(countSql);
        if (paras != null) {
            int i = 1;
            for (Object para : paras) {
                query.setParameter(i++, para);
            }
        }
        List<Object> resultList2 = query.getResultList();
        BigInteger total = (BigInteger) resultList2.get(0);

        return new PageImpl<T>(
                mapToObjectList(resultList, cls),
                new PageRequest(page - 1, size), total.longValue());
    }


    private static String camelCaseName(String underscoreName) {
        StringBuilder result = new StringBuilder();
        if (underscoreName != null && underscoreName.length() > 0) {
            boolean flag = false;
            for (int i = 0; i < underscoreName.length(); i++) {
                char ch = underscoreName.charAt(i);
                if ("_".charAt(0) == ch) {
                    flag = true;
                } else {
                    if (flag) {
                        result.append(Character.toUpperCase(ch));
                        flag = false;
                    } else {
                        result.append(ch);
                    }
                }
            }
        }
        return result.toString();
    }

    private static <T> List<T> mapToObjectList(List<Map<String, Object>> resultList, Class<T> cls) {
        List<T> list = new ArrayList<>();
        for (Map<String, Object> map : resultList) {
            Map<String, Object> map2 = new HashMap<>();
            for (String key : map.keySet()) {
                String newKey = camelCaseName(key);
                map2.put(newKey, map.get(key));
            }

            T t = null;
            try {
                t = cls.newInstance();
                BeanUtils.populate(t, map2);
            } catch (Exception e) {
                e.printStackTrace();
            }
            list.add(t);
        }
        return list;
    }
}
